package com.cloudft.http.services;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Properties;

import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.util.Log;

import com.cloudft.http.R;
import com.cloudft.http.config.CacheConfig;
import com.cloudft.http.models.CFHttpException;
import com.cloudft.http.models.HTTPRequest;
import com.cloudft.http.models.HTTPResponse;


/**
 * CacheService - This class is used for fetching the required HTTPResponse object from the cache/server.
 * It first looks in the local cache and if cached HHTPResponse object is present in the cache and is still alive, then 
 * that is returned to the user, else it is fetched from the server and added to the cache. 
 */
public class CacheService {

	public static int maxCacheEntries = -1;
	public static int maxCacheSize = -1;
	private static CacheConfig defaultCacheConfig = null;
	
	public static final String ENABLE_HEURISTIC_CACHING = "enableHeuristicCaching";
	public static final String TIME_TO_LIVE = "timeToLive";

	//TODO: Read the default cache config from local property file
	/**
	 * CacheService() - The constructor for the CacheService class sets the default cache configuration
	 * by reading it from the local configuration file. If no configuration file is present for the default
	 * configuration, then no default configuration is set. 
	 */
	public CacheService(Context context){

		if(defaultCacheConfig == null){

			defaultCacheConfig = new CacheConfig();
			
			try {
			    InputStream rawResource = context.getResources().openRawResource(R.raw.cacheconfig);
			    Properties properties = new Properties();
			    properties.load(rawResource);
			    Log.i("CacheService", "The properties are now loaded");
			    Log.i("CacheService", "properties: " + properties);
			    
			    defaultCacheConfig.setEnableHeuristicCaching(Boolean.valueOf(properties.getProperty(ENABLE_HEURISTIC_CACHING)));
				defaultCacheConfig.setTimeToLive(Long.valueOf(properties.getProperty(TIME_TO_LIVE)));
			} catch (NotFoundException e) {
				Log.i("Did not find raw resource: ", e.getMessage());
			} catch (IOException e) {
				Log.i("Failed to open property file", e.getMessage());
			}

		}
	}
	
	/**
	 * getDefaultCacheConfig() - Method to get the default cache configuration
	 */
	public CacheConfig getDefaultCacheConfig() {
		return defaultCacheConfig;
	}

	/**
	 * fetchCachedData() - This method gets the required HTTPResponse for the given request. It firsts 
	 * looks in the local cache for the object and return the deserialized HTTPResponse object if it is present 
	 * in the cache and is not expired. On the other hand, if HTTPResponse is not there in the cache or has expired, it
	 * then fetched it from the server.
	 * @param HTTPRequest - Request object created by the calling service/activity
	 * @param Context - Context object passed by the calling service/activity
	 * @return HTTPResponse - HTTPResponse object returned by the cache/http service
	 * @throws CFHttpException 
	 */
	public HTTPResponse fetchCachedData(HTTPRequest request,Context context) throws CFHttpException{

		HTTPResponse response = null;

		//First look in local cache
		response = lookupInCache(request,context);

		if(response == null){

			//Fetch latest data from the server
			response = fetchServerData(request);
			
			//Add the latest data into the local cache
			if((response != null && response.getTimeToLive() != 0) || //Add to cache if the server cache control is non-zero
					(request.getCacheConfig() != null && request.getCacheConfig().getEnableHeuristicCaching()) || //Add to cache if heuristics is enabled in the request
					(request.getCacheConfig() == null && defaultCacheConfig != null && defaultCacheConfig.getEnableHeuristicCaching())) { //Add to cache if request cache config is null and default cache is set

				addToCache(response,request,context);
			}

		}else{
			response.setFromCache(true);
		}
		return response;
	}

	/**
	 * lookupInCache() - This method looks for the cached data locally and returns the serialized
	 * HTTPResponse if object is present in the cache and is still alive
	 * @param HTTPRequest - Request object created by the calling service/activity
	 * @param Context - Context object passed by the calling service/activity
	 * @return HTTPResponse - De-serialized HTTPResponse object 
	 */
	private HTTPResponse lookupInCache(HTTPRequest request,Context context){

		String url = request.getUrl();
		try {

			//Read the serialized file
			String fileName = URLEncoder.encode(url + ".ser", "UTF-8");
			StorageService storageService = new StorageService();

			HTTPResponse response = (HTTPResponse)storageService.deserializeObject(fileName,context);
			if(response == null){
				return null;
			}

			// we return the response from cache, if network is not available
			if(!APIService.isNetworkAvailable(context)){
				return response;
			}
			
			//Compare the timestamp
			Date now = new Date();
			if((now.getTime() - response.getTimeStamp()) <= response.getTimeToLive()){
				return response;

			}else{
				//Fetch the latest data from the server, if cache has expired
				return null;
			}

		} catch (UnsupportedEncodingException e) {
			Log.i("CacheService UnsupportedEncodingException :", e.getMessage());
		}

		return null;
	}

	/**
	 * fetchServerData() method- This is the private method of the CacheService that  calls the
	 * HTTPService to get the latest data from the server
	 * @param HTTPRequest - Request object created by the calling service/activity
	 * @return HTTPResponse - Response object created by the HTTPService
	 * @throws CFHttpException 
	 */
	private HTTPResponse fetchServerData(HTTPRequest request) throws CFHttpException{
		HTTPService httpService = new HTTPService();
		return httpService.fetchServerData(request);
	}

	/**
	 * addToCache() method - This method adds the HTTPResponse from the server and serializes it 
	 * @param HTTPResponse - Response object created by the HTTPService
	 * @param HTTPRequest - Request object created by the calling service/activity
	 * @param Context - Context object passed by the calling service/activity
	 */
	private void addToCache(HTTPResponse response, HTTPRequest request , Context context){

		String fileName;
		try {

			StorageService storageService = new StorageService();
			fileName = URLEncoder.encode(response.getUrl() + ".ser", "UTF-8");

			// Read ttl from server response  and if no value is set by the server then read it from heuristics
			if(response.getTimeToLive() == 0){

				if(request.getCacheConfig() != null && request.getCacheConfig().getTimeToLive() > 0){
					response.setTimeToLive(request.getCacheConfig().getTimeToLive());
				}else{
					if(defaultCacheConfig != null && defaultCacheConfig.getTimeToLive() > 0){
						response.setTimeToLive(defaultCacheConfig.getTimeToLive());
					}	
				}
			}

			//Serialize the HTTPResponse object with url as the filename
			storageService.serializeObject(fileName, response,context);

		} catch (UnsupportedEncodingException e) {

		}
	}

}
